/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.eclipse.imagen;

import java.awt.Point;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BandedSampleModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RasterFormatException;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import org.eclipse.imagen.media.util.DataBufferUtils;

/**
 * A convenience class for the construction of various types of <code>WritableRaster</code> and <code>SampleModel</code>
 * objects.
 *
 * <p>This class provides the capability of creating <code>Raster</code>s with the enumerated data types in the
 * java.awt.image.DataBuffer.
 *
 * <p>In some cases, instances of <code>ComponentSampleModelJAI</code>, a subclass of <code>
 * java.awt.image.ComponentSampleModel</code> are instantiated instead of <code>java.awt.image.BandedSampleModel</code>
 * in order to work around bugs in the current release of the Java 2 SDK.
 */
public class RasterFactory {

    /**
     * Creates a <code>WritableRaster</code> based on a <code>PixelInterleavedSampleModel</code> with the specified data
     * type, width, height, and number of bands.
     *
     * <p>The upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. If
     * <code>location</code> is <code>null</code>, (0, 0) will be used. The <code>dataType</code> parameter should be
     * one of the enumerated values defined in the <code>DataBuffer</code> class.
     *
     * @param dataType The data type of the <code>SampleModel</code>, one of <code>DataBuffer.TYPE_BYTE</code>, <code>
     *     TYPE_USHORT</code>, <code>TYPE_SHORT</code>, <code>TYPE_INT</code>, <code>TYPE_FLOAT</code>, or <code>
     *     TYPE_DOUBLE</code>.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param numBands The desired number of bands.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException if <code>numbands</code> is <code><1</code>.
     */
    public static WritableRaster createInterleavedRaster(
            int dataType, int width, int height, int numBands, Point location) {
        if (numBands < 1) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory0"));
        }
        int[] bandOffsets = new int[numBands];
        for (int i = 0; i < numBands; i++) {
            bandOffsets[i] = numBands - 1 - i;
        }
        return createInterleavedRaster(dataType, width, height, width * numBands, numBands, bandOffsets, location);
    }

    /**
     * Creates a <code>WritableRaster</code> based on a <code>PixelInterleavedSampleModel</code> with the specified data
     * type, width, height, scanline stride, pixel stride, and band offsets. The number of bands is inferred from
     * bandOffsets.length.
     *
     * <p>The upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. If
     * <code>location</code> is <code>null</code>, (0, 0) will be used. The <code>dataType</code> parameter should be
     * one of the enumerated values defined in the <code>DataBuffer</code> class.
     *
     * @param dataType The data type of the <code>WritableRaster</code>, one of the enumerated dataType values in
     *     java.awt.image.DataBuffer.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param scanlineStride The desired scanline stride.
     * @param pixelStride The desired pixel stride.
     * @param bandOffsets An array of <code>int</code>s indicating the relative offsets of the bands within a pixel.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException if <code>bandOffsets</code> is <code>null</code>, <code>dataType</code> is not
     *     one of the enumerated dataType value of java.awt.image.DataBuffer.
     * @throws IllegalArgumentException if the number of array elements required by the returned <code>WritableRaster
     *     </code> would exceed <code>Integer.MAX_VALUE</code>.
     */
    public static WritableRaster createInterleavedRaster(
            int dataType,
            int width,
            int height,
            int scanlineStride,
            int pixelStride,
            int bandOffsets[],
            Point location) {

        if (bandOffsets == null) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory4"));
        }

        DataBuffer d;
        int bands = bandOffsets.length;

        int maxBandOff = bandOffsets[0];
        for (int i = 1; i < bands; i++) {
            if (bandOffsets[i] > maxBandOff) {
                maxBandOff = bandOffsets[i];
            }
        }

        long lsize = (long) maxBandOff + (long) scanlineStride * (height - 1) + (long) pixelStride * (width - 1) + 1L;
        if (lsize > (long) Integer.MAX_VALUE) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory16"));
        }
        int size = (int) lsize;

        switch (dataType) {
            case DataBuffer.TYPE_BYTE:
                d = new DataBufferByte(size);
                break;

            case DataBuffer.TYPE_USHORT:
                d = new DataBufferUShort(size);
                break;

            case DataBuffer.TYPE_SHORT:
                d = new DataBufferShort(size);
                break;

            case DataBuffer.TYPE_INT:
                d = new DataBufferInt(size);
                break;

            case DataBuffer.TYPE_FLOAT:
                d = DataBufferUtils.createDataBufferFloat(size);
                break;

            case DataBuffer.TYPE_DOUBLE:
                d = DataBufferUtils.createDataBufferDouble(size);
                break;

            default:
                throw new IllegalArgumentException(JaiI18N.getString("RasterFactory3"));
        }

        return createInterleavedRaster(d, width, height, scanlineStride, pixelStride, bandOffsets, location);
    }

    /**
     * Creates a <code>WritableRaster</code> based on a <code>ComponentSampleModel</code> with the specified data type,
     * width, height, and number of bands.
     *
     * <p>Note that the <code>Raster</code>'s <code>SampleModel</code> will be of type <code>ComponentSampleModel</code>
     * , not <code>BandedSampleModel</code> as might be expected.
     *
     * <p>The upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. If
     * <code>location</code> is <code>null</code>, (0, 0) will be used. The <code>dataType</code> parameter should be
     * one of the enumerated values defined in the <code>DataBuffer</code> class.
     *
     * @param dataType The data type of the <code>WritableRaster</code>, one of the enumerated dataType values in
     *     java.awt.image.DataBuffer.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param bands The desired number of bands.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException if <code>bands</code> is <code><1</code>.
     */
    public static WritableRaster createBandedRaster(int dataType, int width, int height, int bands, Point location) {
        if (bands < 1) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory0"));
        }
        int[] bankIndices = new int[bands];
        int[] bandOffsets = new int[bands];
        for (int i = 0; i < bands; i++) {
            bankIndices[i] = i;
            bandOffsets[i] = 0;
        }

        return createBandedRaster(dataType, width, height, width, bankIndices, bandOffsets, location);
    }

    /**
     * Creates a <code>WritableRaster</code> based on a <code>ComponentSampleModel</code> with the specified data type,
     * width, height, scanline stride, bank indices and band offsets. The number of bands is inferred from <code>
     * bankIndices.length</code> and <code>bandOffsets.length</code>, which must be the same.
     *
     * <p>Note that the <code>Raster</code>'s <code>SampleModel</code> will be of type <code>ComponentSampleModel</code>
     * , not <code>BandedSampleModel</code> as might be expected.
     *
     * <p>The upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. The
     * <code>dataType</code> parameter should be one of the enumerated values defined in the <code>DataBuffer</code>
     * class.
     *
     * @param dataType The data type of the <code>WritableRaster</code>, one of the enumerated dataType values in
     *     java.awt.image.DataBuffer.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param scanlineStride The desired scanline stride.
     * @param bankIndices An array of <code>int</code>s indicating the bank index for each band.
     * @param bandOffsets An array of <code>int</code>s indicating the relative offsets of the bands within a pixel.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException if <code>bankIndices</code> is <code>null</code>, <code>bandOffsets</code> is
     *     <code>null</code>, if <code>bandOffsets.length</code> is <code>!=</code> <code>bankIndices.length</code>, if
     *     <code>dataType</code> is not one of the enumerated datatypes of java.awt.image.DataBuffer.
     */
    public static WritableRaster createBandedRaster(
            int dataType,
            int width,
            int height,
            int scanlineStride,
            int bankIndices[],
            int bandOffsets[],
            Point location) {
        DataBuffer d;
        int bands = bandOffsets.length;

        if (bankIndices == null) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory1"));
        }
        if (bandOffsets == null) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory4"));
        }

        if (bandOffsets.length != bankIndices.length) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory2"));
        }

        // Figure out the #banks and the largest band offset
        int maxBank = bankIndices[0];
        int maxBandOff = bandOffsets[0];
        for (int i = 1; i < bands; i++) {
            if (bankIndices[i] > maxBank) {
                maxBank = bankIndices[i];
            }
            if (bandOffsets[i] > maxBandOff) {
                maxBandOff = bandOffsets[i];
            }
        }

        int banks = maxBank + 1;
        long lsize = (long) maxBandOff + (long) scanlineStride * (height - 1) + (long) (width - 1) + 1L;
        if (lsize > (long) Integer.MAX_VALUE) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory16"));
        }
        int size = (int) lsize;

        switch (dataType) {
            case DataBuffer.TYPE_BYTE:
                d = new DataBufferByte(size, banks);
                break;

            case DataBuffer.TYPE_USHORT:
                d = new DataBufferUShort(size, banks);
                break;

            case DataBuffer.TYPE_SHORT:
                d = new DataBufferShort(size, banks);
                break;

            case DataBuffer.TYPE_INT:
                d = new DataBufferInt(size, banks);
                break;

            case DataBuffer.TYPE_FLOAT:
                d = DataBufferUtils.createDataBufferFloat(size, banks);
                break;

            case DataBuffer.TYPE_DOUBLE:
                d = DataBufferUtils.createDataBufferDouble(size, banks);
                break;

            default:
                throw new IllegalArgumentException(JaiI18N.getString("RasterFactory3"));
        }

        return createBandedRaster(d, width, height, scanlineStride, bankIndices, bandOffsets, location);
    }

    /**
     * Creates a <code>WritableRaster</code> based on a <code>SinglePixelPackedSampleModel</code> with the specified
     * data type, width, height, and band masks. The number of bands is inferred from <code>bandMasks.length</code>.
     *
     * <p>The upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. If
     * <code>location</code> is <code>null</code>, (0, 0) will be used. The <code>dataType</code> parameter should be
     * one of the enumerated values defined in the <code>DataBuffer</code> class.
     *
     * @param dataType The data type of the <code>WritableRaster</code>, one of <code>DataBuffer.TYPE_BYTE</code>,
     *     <code>TYPE_USHORT</code> or <code>TYPE_INT</code>.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException is thrown if the <code>dataType</code> is not of either TYPE_BYTE or TYPE_USHORT
     *     or TYPE_INT.
     */
    public static WritableRaster createPackedRaster(
            int dataType, int width, int height, int bandMasks[], Point location) {
        return Raster.createPackedRaster(dataType, width, height, bandMasks, location);
    }

    /**
     * Creates a <code>WritableRaster</code> based on a packed <code>SampleModel</code> with the specified data type,
     * width, height, number of bands, and bits per band. If the number of bands is one, the <code>SampleModel</code>
     * will be a <code>MultiPixelPackedSampleModel</code>.
     *
     * <p>If the number of bands is more than one, the <code>SampleModel</code> will be a <code>
     * SinglePixelPackedSampleModel</code>, with each band having <code>bitsPerBand</code> bits. In either case, the
     * requirements on <code>dataType</code> and <code>bitsPerBand</code> imposed by the corresponding <code>SampleModel
     * </code> must be met.
     *
     * <p>The upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. If
     * <code>location</code> is <code>null</code>, (0, 0) will be used. The <code>dataType</code> parameter should be
     * one of the enumerated values defined in the <code>DataBuffer</code> class.
     *
     * @param dataType The data type of the <code>WritableRaster</code>, one of <code>DataBuffer.TYPE_BYTE</code>,
     *     <code>TYPE_USHORT</code> or <code>TYPE_INT</code>.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param numBands The desired number of bands.
     * @param bitsPerBand The number of bits per band.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException is thrown if the <code>dataType</code> is not of either TYPE_BYTE or TYPE_USHORT
     *     or TYPE_INT.
     * @throws IllegalArgumentException is thrown if bitsPerBand is negative or zero.
     */
    public static WritableRaster createPackedRaster(
            int dataType, int width, int height, int numBands, int bitsPerBand, Point location) {
        if (bitsPerBand <= 0) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory15"));
        }

        return Raster.createPackedRaster(dataType, width, height, numBands, bitsPerBand, location);
    }

    /**
     * Creates a <code>WritableRaster</code> based on a <code>PixelInterleavedSampleModel</code> with the specified
     * <code>DataBuffer</code>, width, height, scanline stride, pixel stride, and band offsets. The number of bands is
     * inferred from <code>bandOffsets.length</code>. The upper left corner of the <code>WritableRaster</code> is given
     * by the <code>location</code> argument. If <code>location</code> is <code>null</code>, (0, 0) will be used.
     *
     * @param dataBuffer The <code>DataBuffer</code> to be used.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param scanlineStride The desired scanline stride.
     * @param pixelStride The desired pixel stride.
     * @param bandOffsets An array of <code>int</code>s indicating the relative offsets of the bands within a pixel.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException if <code>bandOffsets</code> is <code>null</code>, if <code>pixelStride*width
     *     </code> is <code>></code> <code>scanlineStride</code>, if <code>dataType</code>of the DataBuffer is not one
     *     the enumerated dataType value of java.awt.image.DataBuffer.
     */
    public static WritableRaster createInterleavedRaster(
            DataBuffer dataBuffer,
            int width,
            int height,
            int scanlineStride,
            int pixelStride,
            int bandOffsets[],
            Point location) {

        if (bandOffsets == null) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory4"));
        }
        if (location == null) {
            location = new Point(0, 0);
        }
        int dataType = dataBuffer.getDataType();

        switch (dataType) {
            case DataBuffer.TYPE_BYTE:
            case DataBuffer.TYPE_USHORT:
                PixelInterleavedSampleModel csm = new PixelInterleavedSampleModel(
                        dataType, width, height, pixelStride, scanlineStride, bandOffsets);
                return Raster.createWritableRaster(csm, dataBuffer, location);

            case DataBuffer.TYPE_INT:
            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_FLOAT:
            case DataBuffer.TYPE_DOUBLE:
                int minBandOff = bandOffsets[0];
                int maxBandOff = bandOffsets[0];
                for (int i = 1; i < bandOffsets.length; i++) {
                    minBandOff = Math.min(minBandOff, bandOffsets[i]);
                    maxBandOff = Math.max(maxBandOff, bandOffsets[i]);
                }
                maxBandOff -= minBandOff;
                if (maxBandOff > scanlineStride) {
                    throw new IllegalArgumentException(JaiI18N.getString("RasterFactory5"));
                }
                if (pixelStride * width > scanlineStride) {
                    throw new IllegalArgumentException(JaiI18N.getString("RasterFactory6"));
                }
                if (pixelStride < maxBandOff) {
                    throw new IllegalArgumentException(JaiI18N.getString("RasterFactory7"));
                }

                SampleModel sm =
                        new ComponentSampleModelJAI(dataType, width, height, pixelStride, scanlineStride, bandOffsets);
                return Raster.createWritableRaster(sm, dataBuffer, location);

            default:
                throw new IllegalArgumentException(JaiI18N.getString("RasterFactory3"));
        }
    }

    /**
     * Creates a <code>WritableRaster</code> based on a <code>ComponentSampleModel</code> with the specified <code>
     * DataBuffer</code>, width, height, scanline stride, bank indices, and band offsets. The number of bands is
     * inferred from <code>bankIndices.length</code> and <code>bandOffsets.length</code>, which must be the same. The
     * upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. If <code>
     * location</code> is <code>null</code>, (0, 0) will be used.
     *
     * <p>Note that the <code>Raster</code>'s <code>SampleModel</code> will be of type <code>ComponentSampleModel</code>
     * , not <code>BandedSampleModel</code> as might be expected.
     *
     * @param dataBuffer The <code>DataBuffer</code> to be used.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param scanlineStride The desired scanline stride.
     * @param bankIndices An array of <code>int</code>s indicating the bank index for each band.
     * @param bandOffsets An array of <code>int</code>s indicating the relative offsets of the bands within a pixel.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException if <code>bankIndices</code> is <code>null</code>, if <code>bandOffsets</code> is
     *     <code>null</code>, if <code>bandOffsets.length</code> is <code>!=</code> <code>bankIndices.length</code>, if
     *     <code>dataType</code> is not one of the enumerated datatypes of java.awt.image.DataBuffer.
     */
    public static WritableRaster createBandedRaster(
            DataBuffer dataBuffer,
            int width,
            int height,
            int scanlineStride,
            int bankIndices[],
            int bandOffsets[],
            Point location) {
        if (location == null) {
            location = new Point(0, 0);
        }
        int dataType = dataBuffer.getDataType();

        if (bankIndices == null) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory1"));
        }
        if (bandOffsets == null) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory4"));
        }

        int bands = bankIndices.length;
        if (bandOffsets.length != bands) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory2"));
        }

        SampleModel bsm =
                new ComponentSampleModelJAI(dataType, width, height, 1, scanlineStride, bankIndices, bandOffsets);

        switch (dataType) {
            case DataBuffer.TYPE_BYTE:
            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_INT:
            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_FLOAT:
            case DataBuffer.TYPE_DOUBLE:
                return Raster.createWritableRaster(bsm, dataBuffer, location);

            default:
                throw new IllegalArgumentException(JaiI18N.getString("RasterFactory3"));
        }
    }

    /**
     * Creates a <code>WritableRaster</code> based on a <code>SinglePixelPackedSampleModel</code> with the specified
     * <code>DataBuffer</code>, width, height, scanline stride, and band masks. The number of bands is inferred from
     * <code>bandMasks.length</code>. The upper left corner of the <code>WritableRaster</code> is given by the <code>
     * location</code> argument. If <code>location</code> is <code>null</code>, (0, 0) will be used.
     *
     * @param dataBuffer The <code>DataBuffer</code> to be used.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param scanlineStride The desired scanline stride.
     * @param bandMasks An array of <code>int</code>s indicating the bitmasks for each band within a pixel.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException is thrown if the <code>dataType</code> is not of either TYPE_BYTE or TYPE_USHORT
     *     or TYPE_INT.
     */
    public static WritableRaster createPackedRaster(
            DataBuffer dataBuffer, int width, int height, int scanlineStride, int bandMasks[], Point location) {
        return Raster.createPackedRaster(dataBuffer, width, height, scanlineStride, bandMasks, location);
    }

    /**
     * Creates a <code>WritableRaster</code> based on a <code>MultiPixelPackedSampleModel</code> with the specified
     * <code>DataBuffer</code>, width, height, and bits per pixel. The upper left corner of the <code>WritableRaster
     * </code> is given by the <code>location</code> argument. If <code>location</code> is <code>null</code>, (0, 0)
     * will be used.
     *
     * @param dataBuffer The <code>DataBuffer</code> to be used.
     * @param width The desired width of the <code>WritableRaster</code>.
     * @param height The desired height of the <code>WritableRaster</code>.
     * @param bitsPerPixel The desired pixel depth.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     * @throws IllegalArgumentException is thrown if the <code>dataType</code> of the <code>dataBuffer</code> is not of
     *     either TYPE_BYTE or TYPE_USHORT or TYPE_INT.
     */
    public static WritableRaster createPackedRaster(
            DataBuffer dataBuffer, int width, int height, int bitsPerPixel, Point location) {
        return Raster.createPackedRaster(dataBuffer, width, height, bitsPerPixel, location);
    }

    /**
     * Creates a <code>WritableRaster</code> with the specified <code>SampleModel</code> and <code>DataBuffer</code>.
     * The upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. If
     * <code>location</code> is <code>null</code>, (0, 0) will be used.
     *
     * @param sampleModel The <code>SampleModel</code> to be used.
     * @param dataBuffer The <code>DataBuffer</code> to be used.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     */
    public static Raster createRaster(SampleModel sampleModel, DataBuffer dataBuffer, Point location) {
        return Raster.createRaster(sampleModel, dataBuffer, location);
    }

    /**
     * Creates a <code>WritableRaster</code> with the specified <code>SampleModel</code>. The upper left corner of the
     * <code>WritableRaster</code> is given by the <code>location</code> argument. If <code>location</code> is <code>
     * null</code>, (0, 0) will be used.
     *
     * @param sampleModel The <code>SampleModel</code> to use.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     */
    public static WritableRaster createWritableRaster(SampleModel sampleModel, Point location) {
        if (location == null) {
            location = new Point(0, 0);
        }

        return createWritableRaster(sampleModel, sampleModel.createDataBuffer(), location);
    }

    /**
     * Creates a <code>WritableRaster</code> with the specified <code>SampleModel</code> and <code>DataBuffer</code>.
     * The upper left corner of the <code>WritableRaster</code> is given by the <code>location</code> argument. If
     * <code>location</code> is <code>null</code>, (0, 0) will be used.
     *
     * @param sampleModel The <code>SampleModel</code> to be used.
     * @param dataBuffer The <code>DataBuffer</code> to be used.
     * @param location A <code>Point</code> indicating the starting coordinates of the <code>WritableRaster</code>.
     */
    public static WritableRaster createWritableRaster(SampleModel sampleModel, DataBuffer dataBuffer, Point location) {
        return Raster.createWritableRaster(sampleModel, dataBuffer, location);
    }

    /**
     * Returns a new WritableRaster which shares all or part of the supplied WritableRaster's DataBuffer. The new
     * WritableRaster will possess a reference to the supplied WritableRaster, accessible through its getParent() and
     * getWritableParent() methods.
     *
     * <p>This method provides a workaround for a bug in the implementation of WritableRaster.createWritableChild in the
     * initial relase of the Java2 platform.
     *
     * <p>The <code>parentX</code>, <code>parentY</code>, <code>width</code> and <code>height</code> parameters form a
     * Rectangle in this WritableRaster's coordinate space, indicating the area of pixels to be shared. An error will be
     * thrown if this Rectangle is not contained with the bounds of the supplied WritableRaster.
     *
     * <p>The new WritableRaster may additionally be translated to a different coordinate system for the plane than that
     * used by the supplied WritableRaster. The childMinX and childMinY parameters give the new (x, y) coordinate of the
     * upper-left pixel of the returned WritableRaster; the coordinate (childMinX, childMinY) in the new WritableRaster
     * will map to the same pixel as the coordinate (parentX, parentY) in the supplied WritableRaster.
     *
     * <p>The new WritableRaster may be defined to contain only a subset of the bands of the supplied WritableRaster,
     * possibly reordered, by means of the bandList parameter. If bandList is null, it is taken to include all of the
     * bands of the supplied WritableRaster in their current order.
     *
     * <p>To create a new WritableRaster that contains a subregion of the supplied WritableRaster, but shares its
     * coordinate system and bands, this method should be called with childMinX equal to parentX, childMinY equal to
     * parentY, and bandList equal to null.
     *
     * @param raster The parent WritableRaster.
     * @param parentX X coordinate of the upper left corner of the shared rectangle in this WritableRaster's
     *     coordinates.
     * @param parentY Y coordinate of the upper left corner of the shared rectangle in this WritableRaster's
     *     coordinates.
     * @param width Width of the shared rectangle starting at (<code>parentX</code>, <code>parentY</code>).
     * @param height Height of the shared rectangle starting at (<code>parentX</code>, <code>parentY</code>).
     * @param childMinX X coordinate of the upper left corner of the returned WritableRaster.
     * @param childMinY Y coordinate of the upper left corner of the returned WritableRaster.
     * @param bandList Array of band indices, or null to use all bands.
     * @throws RasterFormatException if the subregion is outside of the raster bounds.
     */
    public static WritableRaster createWritableChild(
            WritableRaster raster,
            int parentX,
            int parentY,
            int width,
            int height,
            int childMinX,
            int childMinY,
            int bandList[]) {
        // Simply forward the call to the equivalent WritableRaster method.
        // The WritableRaster bug referred to in the javadoc was 4212434
        // and was fixed in Java SE 1.3, which is the minimum version
        // required for JAI.
        return raster.createWritableChild(parentX, parentY, width, height, childMinX, childMinY, bandList);
    }

    /**
     * Creates a banded <code>SampleModel</code> with a given data type, width, height, number of bands, bank indices,
     * and band offsets.
     *
     * <p>Note that the returned <code>SampleModel</code> will be of type <code>ComponentSampleModel</code>, not <code>
     * BandedSampleModel</code> as might be expected. Its behavior will be equivalent to that of a <code>
     * BandedSampleModel</code>, and in particular its pixel stride will always be 1.
     *
     * @param dataType The data type of the <code>SampleModel</code>, one of <code>DataBuffer.TYPE_BYTE</code>, <code>
     *     TYPE_USHORT</code>, <code>TYPE_SHORT</code>, <code>TYPE_INT</code>, <code>TYPE_FLOAT</code>, or <code>
     *     TYPE_DOUBLE</code>.
     * @param width The desired width of the <code>SampleModel</code>.
     * @param height The desired height of the <code>SampleModel</code>.
     * @param numBands The desired number of bands.
     * @param bankIndices An array of <code>int</code>s indicating the bank index for each band.
     * @param bandOffsets An array of <code>int</code>s indicating the relative offsets of the bands within a pixel.
     * @throws IllegalArgumentException if <code>numBands</code> is <code><1</code>, if <code>bandOffsets.length</code>
     *     is <code>!=</code> <code>bankIndices.length</code>.
     */
    public static SampleModel createBandedSampleModel(
            int dataType, int width, int height, int numBands, int bankIndices[], int bandOffsets[]) {
        if (numBands < 1) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory0"));
        }
        if (bankIndices == null) {
            bankIndices = new int[numBands];
            for (int i = 0; i < numBands; i++) {
                bankIndices[i] = i;
            }
        }
        if (bandOffsets == null) {
            bandOffsets = new int[numBands];
            for (int i = 0; i < numBands; i++) {
                bandOffsets[i] = 0;
            }
        }
        if (bandOffsets.length != bankIndices.length) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory2"));
        }
        return new ComponentSampleModelJAI(dataType, width, height, 1, width, bankIndices, bandOffsets);
    }

    /**
     * Creates a banded <code>SampleModel</code> with a given data type, width, height, and number of bands. The bank
     * indices and band offsets are set to default values.
     *
     * <p>Note that the returned <code>SampleModel</code> will be of type <code>ComponentSampleModel</code>, not <code>
     * BandedSampleModel</code> as might be expected. Its behavior will be equivalent to that of a <code>
     * BandedSampleModel</code>, and in particular its pixel stride will always be 1.
     *
     * @param dataType The data type of the <code>SampleModel</code>, one of <code>DataBuffer.TYPE_BYTE</code>, <code>
     *     TYPE_USHORT</code>, <code>TYPE_SHORT</code>, <code>TYPE_INT</code>, <code>TYPE_FLOAT</code>, or <code>
     *     TYPE_DOUBLE</code>.
     * @param width The desired width of the <code>SampleModel</code>.
     * @param height The desired height of the <code>SampleModel</code>.
     * @param numBands The desired number of bands.
     */
    public static SampleModel createBandedSampleModel(int dataType, int width, int height, int numBands) {
        return createBandedSampleModel(dataType, width, height, numBands, null, null);
    }

    /**
     * Creates a pixel interleaved <code>SampleModel</code> with a given data type, width, height, pixel and scanline
     * strides, and band offsets.
     *
     * @param dataType The data type of the <code>SampleModel</code>, one of <code>DataBuffer.TYPE_BYTE</code>, <code>
     *     TYPE_USHORT</code>, <code>TYPE_SHORT</code>, <code>TYPE_INT</code>, <code>TYPE_FLOAT</code>, or <code>
     *     TYPE_DOUBLE</code>.
     * @param width The desired width of the <code>SampleModel</code>.
     * @param height The desired height of the <code>SampleModel</code>.
     * @param pixelStride The desired pixel stride.
     * @param scanlineStride The desired scanline stride.
     * @param bandOffsets An array of <code>int</code>s indicating the relative offsets of the bands within a pixel.
     * @throws IllegalArgumentException if <code>bandOffsets</code> is <code>null</code>, if the <code>pixelStride*width
     *     </code> is <code>></code> than <code>scanlineStride</code>, if the <code>dataType</code> is not one of the
     *     above mentioned datatypes.
     */
    public static SampleModel createPixelInterleavedSampleModel(
            int dataType, int width, int height, int pixelStride, int scanlineStride, int bandOffsets[]) {
        if (bandOffsets == null) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory4"));
        }
        int minBandOff = bandOffsets[0];
        int maxBandOff = bandOffsets[0];
        for (int i = 1; i < bandOffsets.length; i++) {
            minBandOff = Math.min(minBandOff, bandOffsets[i]);
            maxBandOff = Math.max(maxBandOff, bandOffsets[i]);
        }
        maxBandOff -= minBandOff;
        if (maxBandOff > scanlineStride) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory5"));
        }
        if (pixelStride * width > scanlineStride) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory6"));
        }
        if (pixelStride < maxBandOff) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory7"));
        }

        switch (dataType) {
            case DataBuffer.TYPE_BYTE:
            case DataBuffer.TYPE_USHORT:
                return new PixelInterleavedSampleModel(
                        dataType, width, height, pixelStride, scanlineStride, bandOffsets);
            case DataBuffer.TYPE_INT:
            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_FLOAT:
            case DataBuffer.TYPE_DOUBLE:
                return new ComponentSampleModelJAI(dataType, width, height, pixelStride, scanlineStride, bandOffsets);
            default:
                throw new IllegalArgumentException(JaiI18N.getString("RasterFactory3"));
        }
    }

    /**
     * Creates a pixel interleaved <code>SampleModel</code> with a given data type, width, height, and number of bands.
     * The pixel stride, scanline stride, and band offsets are set to default values.
     *
     * @param dataType The data type of the <code>SampleModel</code>, one of <code>DataBuffer.TYPE_BYTE</code>, <code>
     *     TYPE_USHORT</code>, <code>TYPE_SHORT</code>, <code>TYPE_INT</code>, <code>TYPE_FLOAT</code>, or <code>
     *     TYPE_DOUBLE</code>.
     * @param width The desired width of the <code>SampleModel</code>.
     * @param height The desired height of the <code>SampleModel</code>.
     * @param numBands The desired number of bands.
     * @throws IllegalArgumentException if <code>numBands</code> is <code><1</code>.
     */
    public static SampleModel createPixelInterleavedSampleModel(int dataType, int width, int height, int numBands) {
        if (numBands < 1) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory0"));
        }
        int[] bandOffsets = new int[numBands];
        for (int i = 0; i < numBands; i++) {
            bandOffsets[i] = numBands - 1 - i;
        }

        return createPixelInterleavedSampleModel(dataType, width, height, numBands, numBands * width, bandOffsets);
    }

    /**
     * Creates a component <code>SampleModel</code> with a given data type, width, height, and number of bands that is
     * "compatible" with a given SampleModel.
     *
     * @param sm The <code>SampleModel</code> to be compatible with.
     * @param dataType The data type of the <code>SampleModel</code>, one of <code>DataBuffer.TYPE_BYTE</code>, <code>
     *     TYPE_USHORT</code>, <code>TYPE_SHORT</code>, <code>TYPE_INT</code>, <code>TYPE_FLOAT</code>, or <code>
     *     TYPE_DOUBLE</code>.
     * @param width The desired width of the <code>SampleModel</code>.
     * @param height The desired height of the <code>SampleModel</code>.
     * @param numBands The desired number of bands.
     */
    public static SampleModel createComponentSampleModel(
            SampleModel sm, int dataType, int width, int height, int numBands) {
        if (sm instanceof BandedSampleModel) {
            return createBandedSampleModel(dataType, width, height, numBands);
        } else { // default SampleModel
            return createPixelInterleavedSampleModel(dataType, width, height, numBands);
        }
    }

    /**
     * Creates a component-based <code>ColorModel</code> with a given data type, color space, and transparency type.
     * Currently this method does not support data type <code>DataBuffer.TYPE_SHORT</code>. If useAlpha is false, both
     * premultiplied and transparency input are ignored and they are set to be <code> false</code> and <code>
     *  Transparency.OPQAUE </code>, respectively.
     *
     * @param dataType The data type of the <code>ColorModel</code>, one of <code>DataBuffer.TYPE_BYTE</code>, <code>
     *     TYPE_USHORT</code>, <code>TYPE_INT</code>, <code>TYPE_FLOAT</code>, or <code>TYPE_DOUBLE</code>.
     * @param colorSpace An instance of <code>ColorSpace</code>.
     * @param useAlpha <code>true</code> if alpha is to be used.
     * @param premultiplied <code>true</code> if alpha values are premultiplied. If <code>useAlpha</code> is <code>false
     *     </code>, the value of <code>premultiplied</code> is ignored.
     * @param transparency One of <code>Transparency.OPAQUE</code>, <code>Transparency.BITMASK</code>, or <code>
     *     Transparency.TRANSLUCENT</code>. If <code>useAlpha</code> is <code>false</code>, the value of <code>
     *     transparency</code> is ignored. If <code>useAlpha</code> is <code>true</code>, <code>transparency</code> must
     *     not equal <code>Transparency.OPQAUE</code>.
     * @throws IllegalArgumentExceptionException if <code>colorSpace</code> is <code>null</code>.
     * @throws IllegalArgumentException if <code>transparency</code> has an unknown value, if <code>useAlpha == true
     *     </code> but <code>transparency == Transparency.OPAQUE</code>, or if <code>dataType</code> is not one of the
     *     standard types listed above.
     */
    public static ComponentColorModel createComponentColorModel(
            int dataType, ColorSpace colorSpace, boolean useAlpha, boolean premultiplied, int transparency) {

        if (colorSpace == null) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        if ((transparency != Transparency.OPAQUE)
                && (transparency != Transparency.BITMASK)
                && (transparency != Transparency.TRANSLUCENT)) {
            // Illegal value for transparency
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory13"));
        }

        if (useAlpha && (transparency == Transparency.OPAQUE)) {
            throw new IllegalArgumentException(JaiI18N.getString("RasterFactory14"));
        }

        if (!useAlpha) {
            premultiplied = false;
            transparency = Transparency.OPAQUE;
        }

        int bands = colorSpace.getNumComponents();
        if (useAlpha) {
            ++bands;
        }

        int dataTypeSize = DataBuffer.getDataTypeSize(dataType);
        int[] bits = new int[bands];
        for (int i = 0; i < bands; i++) {
            bits[i] = dataTypeSize;
        }

        switch (dataType) {
            case DataBuffer.TYPE_BYTE:
                return new ComponentColorModel(colorSpace, bits, useAlpha, premultiplied, transparency, dataType);
            case DataBuffer.TYPE_USHORT:
                return new ComponentColorModel(colorSpace, bits, useAlpha, premultiplied, transparency, dataType);
                /// case DataBuffer.TYPE_SHORT:
                /// return new ShortComponentColorModel(colorSpace,
                /// bits,
                /// useAlpha,
                /// premultiplied,
                /// transparency);
            case DataBuffer.TYPE_INT:
                return new ComponentColorModel(colorSpace, bits, useAlpha, premultiplied, transparency, dataType);
            case DataBuffer.TYPE_FLOAT:
                return new FloatDoubleColorModel(colorSpace, useAlpha, premultiplied, transparency, dataType);
            case DataBuffer.TYPE_DOUBLE:
                return new FloatDoubleColorModel(colorSpace, useAlpha, premultiplied, transparency, dataType);
            default:
                throw new IllegalArgumentException(JaiI18N.getString("RasterFactory8"));
        }
    }
}
